using System;
using System.Collections.Generic;
using System.Text;
using Barrage.Global;
using SlimDX.Direct3D9;
using SlimDX;
using Barrage;

namespace Barrage.Shaders
{

    enum ShaderHandles
    {
        WorldViewProjection=0,
        World=1,
        View=2,
        Projection=3,
        ViewInverse=4,
        WorldInverseTranspose=5,
        Diffuse=6,
        Ambient=7,
        Specular=8,
        Shininess=9,
        LightPosition0=10,
        LightPosition1=11,
        LightPosition2=12,
        LightAmbient0=13,
        LightAmbient1=14,
        LightAmbient2=15,
        LightDiffuse0=16,
        LightDiffuse1=17,
        LightDiffuse2=18,
        LightSpecular0=19,
        LightSpecular1=20,
        LightSpecular2=21,
        LightAttenuation0=22,
        LightAttenuation1=23,
        LightAttenuation2=24,
        ElapsedTime=28,
        Time=29,
        Texture0=30,
        Texture1=31,
        Texture2=32,
        Texture3=33,
        Texture4=34,
        Texture5=35,
        Texture6=36,
        Texture7=37,
        FloatParameter0=38,
        FloatParameter1=39,
        FloatParameter2=40,
        FloatParameter3=41,
        FloatParameter4=42,
        FloatParameter5=43,
        FloatParameter6=44,
        FloatParameter7=45,
        
        Float4Parameter0=46,
        Float4Parameter1=47,
        Float4Parameter2=48,
        Float4Parameter3=49,
        Float4Parameter4=50,
        Float4Parameter5=51,
        Float4Parameter6=52,
        Float4Parameter7=53,

        MatrixParameter0=54,
        MatrixParameter1=55,
        MatrixParameter2=56,
        MatrixParameter3=57,
        MatrixParameter4=58,
        MatrixParameter5=59,
        MatrixParameter6=60,
        MatrixParameter7=61,

        Float4ArrayParameter0=62,
        Float4ArrayParameter1=63,
        Float4ArrayParameter2=64,
        Float4ArrayParameter3=65,
        Float4ArrayParameter4=66,
        Float4ArrayParameter5=67,
        Float4ArrayParameter6=68,
        Float4ArrayParameter7=69,

        ViewProjection=70
    }
    /// <summary>
    /// D3DShader implements shaders by loading a .fx file and getting/setting its parameters
    /// </summary>
    public class D3DShader:Shader
    {
        const int HANDLE_NUM = 71;

        EffectHandle[] handles = new EffectHandle[HANDLE_NUM];
        Effect effect;
        /// <summary>
        /// Creates a shader form a .fx file
        /// </summary>
        /// <param name="filename">file path (relative to ShaderPath)</param>
        public D3DShader(string filename)
        {            
            string file = Store.GetFilePath("ShaderPath", filename);
            string compilationErrors = "";
            try
            {
                effect = Effect.FromFile(Engine.Device, file, null, null, null, ShaderFlags.None, null, out compilationErrors);
                loadHandles();
            }
            catch (Exception e)
            {
                NLog.LogManager.GetLogger("Barrage::Scene::Shaders::D3DShader").Error("Error creating shader: " + e.Message + " " + compilationErrors);
                effect = null;
            }
        }

        void loadHandles()
        {
            handles[(int)ShaderHandles.WorldViewProjection] = effect.GetParameterBySemantic(null, "WorldViewProjection");
            handles[(int)ShaderHandles.World] = effect.GetParameterBySemantic(null, "World");
            handles[(int)ShaderHandles.View] = effect.GetParameterBySemantic(null, "View");
            handles[(int)ShaderHandles.Projection] = effect.GetParameterBySemantic(null, "Projection");
            handles[(int)ShaderHandles.ViewInverse] = effect.GetParameterBySemantic(null, "ViewInverse");
            handles[(int)ShaderHandles.WorldInverseTranspose] = effect.GetParameterBySemantic(null, "WorldInverseTranspose");
            handles[(int)ShaderHandles.Diffuse] = effect.GetParameterBySemantic(null, "Diffuse");
            handles[(int)ShaderHandles.Ambient] = effect.GetParameterBySemantic(null, "Ambient");
            handles[(int)ShaderHandles.Specular] = effect.GetParameterBySemantic(null, "Specular");
            handles[(int)ShaderHandles.Shininess] = effect.GetParameterBySemantic(null, "Shininess");
            handles[(int)ShaderHandles.LightPosition0] = effect.GetParameterBySemantic(null, "LightPosition0");
            handles[(int)ShaderHandles.LightPosition1] = effect.GetParameterBySemantic(null, "LightPosition2");
            handles[(int)ShaderHandles.LightPosition2] = effect.GetParameterBySemantic(null, "LightPosition1");
            handles[(int)ShaderHandles.LightAmbient0] = effect.GetParameterBySemantic(null, "LightAmbient0");
            handles[(int)ShaderHandles.LightAmbient1] = effect.GetParameterBySemantic(null, "LightAmbient1");
            handles[(int)ShaderHandles.LightAmbient2] = effect.GetParameterBySemantic(null, "LightAmbient2");
            handles[(int)ShaderHandles.LightDiffuse0] = effect.GetParameterBySemantic(null, "LightDiffuse0");
            handles[(int)ShaderHandles.LightDiffuse1] = effect.GetParameterBySemantic(null, "LightDiffuse1");
            handles[(int)ShaderHandles.LightDiffuse2] = effect.GetParameterBySemantic(null, "LightDiffuse2");
            handles[(int)ShaderHandles.LightSpecular0] = effect.GetParameterBySemantic(null, "LightSpecular0");
            handles[(int)ShaderHandles.LightSpecular1] = effect.GetParameterBySemantic(null, "LightSpecular1");
            handles[(int)ShaderHandles.LightSpecular2] = effect.GetParameterBySemantic(null, "LightSpecular2");
            handles[(int)ShaderHandles.LightAttenuation0] = effect.GetParameterBySemantic(null, "LightAttenuation0");
            handles[(int)ShaderHandles.LightAttenuation1] = effect.GetParameterBySemantic(null, "LightAttenuation1");
            handles[(int)ShaderHandles.LightAttenuation2] = effect.GetParameterBySemantic(null, "LightAttenuation2");
            handles[(int)ShaderHandles.LightPosition0] = effect.GetParameterBySemantic(null, "LightPosition0");
            handles[(int)ShaderHandles.LightPosition1] = effect.GetParameterBySemantic(null, "LightPosition1");
            handles[(int)ShaderHandles.LightPosition2] = effect.GetParameterBySemantic(null, "LightPosition2");
            handles[(int)ShaderHandles.ElapsedTime] = effect.GetParameterBySemantic(null, "ElapsedTime");
            handles[(int)ShaderHandles.Time] = effect.GetParameterBySemantic(null, "Time");
            handles[(int)ShaderHandles.Texture0] = effect.GetParameterBySemantic(null, "Texture0");
            handles[(int)ShaderHandles.Texture1] = effect.GetParameterBySemantic(null, "Texture1");
            handles[(int)ShaderHandles.Texture2] = effect.GetParameterBySemantic(null, "Texture2");
            handles[(int)ShaderHandles.Texture3] = effect.GetParameterBySemantic(null, "Texture3");
            handles[(int)ShaderHandles.Texture4] = effect.GetParameterBySemantic(null, "Texture4");
            handles[(int)ShaderHandles.Texture5] = effect.GetParameterBySemantic(null, "Texture5");
            handles[(int)ShaderHandles.Texture6] = effect.GetParameterBySemantic(null, "Texture6");
            handles[(int)ShaderHandles.Texture7] = effect.GetParameterBySemantic(null, "Texture7");

            handles[(int)ShaderHandles.FloatParameter0] = effect.GetParameterBySemantic(null, "FloatParameter0");
            handles[(int)ShaderHandles.FloatParameter1] = effect.GetParameterBySemantic(null, "FloatParameter1");
            handles[(int)ShaderHandles.FloatParameter2] = effect.GetParameterBySemantic(null, "FloatParameter2");
            handles[(int)ShaderHandles.FloatParameter3] = effect.GetParameterBySemantic(null, "FloatParameter3");
            handles[(int)ShaderHandles.FloatParameter4] = effect.GetParameterBySemantic(null, "FloatParameter4");
            handles[(int)ShaderHandles.FloatParameter5] = effect.GetParameterBySemantic(null, "FloatParameter5");
            handles[(int)ShaderHandles.FloatParameter6] = effect.GetParameterBySemantic(null, "FloatParameter6");
            handles[(int)ShaderHandles.FloatParameter7] = effect.GetParameterBySemantic(null, "FloatParameter7");
            
            handles[(int)ShaderHandles.Float4Parameter0] = effect.GetParameterBySemantic(null, "Float4Parameter0");
            handles[(int)ShaderHandles.Float4Parameter1] = effect.GetParameterBySemantic(null, "Float4Parameter1");
            handles[(int)ShaderHandles.Float4Parameter2] = effect.GetParameterBySemantic(null, "Float4Parameter2");
            handles[(int)ShaderHandles.Float4Parameter3] = effect.GetParameterBySemantic(null, "Float4Parameter3");
            handles[(int)ShaderHandles.Float4Parameter4] = effect.GetParameterBySemantic(null, "Float4Parameter4");
            handles[(int)ShaderHandles.Float4Parameter5] = effect.GetParameterBySemantic(null, "Float4Parameter5");
            handles[(int)ShaderHandles.Float4Parameter6] = effect.GetParameterBySemantic(null, "Float4Parameter6");
            handles[(int)ShaderHandles.Float4Parameter7] = effect.GetParameterBySemantic(null, "Float4Parameter7");

            handles[(int)ShaderHandles.MatrixParameter0] = effect.GetParameterBySemantic(null, "MatrixParameter0");
            handles[(int)ShaderHandles.MatrixParameter1] = effect.GetParameterBySemantic(null, "MatrixParameter1");
            handles[(int)ShaderHandles.MatrixParameter2] = effect.GetParameterBySemantic(null, "MatrixParameter2");
            handles[(int)ShaderHandles.MatrixParameter3] = effect.GetParameterBySemantic(null, "MatrixParameter3");
            handles[(int)ShaderHandles.MatrixParameter4] = effect.GetParameterBySemantic(null, "MatrixParameter4");
            handles[(int)ShaderHandles.MatrixParameter5] = effect.GetParameterBySemantic(null, "MatrixParameter5");
            handles[(int)ShaderHandles.MatrixParameter6] = effect.GetParameterBySemantic(null, "MatrixParameter6");
            handles[(int)ShaderHandles.MatrixParameter7] = effect.GetParameterBySemantic(null, "MatrixParameter7");

            handles[(int)ShaderHandles.Float4ArrayParameter0] = effect.GetParameterBySemantic(null, "Float4ArrayParameter0");
            handles[(int)ShaderHandles.Float4ArrayParameter1] = effect.GetParameterBySemantic(null, "Float4ArrayParameter1");
            handles[(int)ShaderHandles.Float4ArrayParameter2] = effect.GetParameterBySemantic(null, "Float4ArrayParameter2");
            handles[(int)ShaderHandles.Float4ArrayParameter3] = effect.GetParameterBySemantic(null, "Float4ArrayParameter3");
            handles[(int)ShaderHandles.Float4ArrayParameter4] = effect.GetParameterBySemantic(null, "Float4ArrayParameter4");
            handles[(int)ShaderHandles.Float4ArrayParameter5] = effect.GetParameterBySemantic(null, "Float4ArrayParameter5");
            handles[(int)ShaderHandles.Float4ArrayParameter6] = effect.GetParameterBySemantic(null, "Float4ArrayParameter6");
            handles[(int)ShaderHandles.Float4ArrayParameter7] = effect.GetParameterBySemantic(null, "Float4ArrayParameter7");

            handles[(int)ShaderHandles.ViewProjection] = effect.GetParameterBySemantic(null, "ViewProjection");

        }

        void fillCustomVariables(Item item, Material material)
        {
            if (material.Technique != null)
            {
                EffectHandle tech=effect.GetTechnique(material.Technique);
                if (tech != null)
                    effect.Technique = tech;
                else effect.Technique = effect.GetTechnique(0);
            }
            else effect.Technique=effect.GetTechnique(0);
            
            for (int i = 0; i < material.FloatParameters.Count; i++)
            {
                EffectHandle handle = handles[(int)ShaderHandles.FloatParameter0 + i];
                if (handle != null) effect.SetValue(handle, material.FloatParameters[i]);
            }

            for (int i = 0; i < material.Vector4Parameters.Count; i++)
            {
                EffectHandle handle = handles[(int)ShaderHandles.Float4Parameter0 + i];
                if (handle != null) effect.SetValue(handle, material.Vector4Parameters[i]);
            }
            
            for (int i = 0; i < material.MatrixParameters.Count; i++)
            {
                EffectHandle handle = handles[(int)ShaderHandles.MatrixParameter0 + i];
                if (handle != null) effect.SetValue(handle, material.MatrixParameters[i]);
            }

            for (int i = 0; i < material.Vector4ArrayParameters.Count; i++)
            {
                EffectHandle handle = handles[(int)ShaderHandles.Float4ArrayParameter0 + i];
                if (handle != null) effect.SetValue(handle, material.Vector4ArrayParameters[i].ToArray());
            }
        }

        void fillStandardVariables(Item item, Material material)
        {
            EffectHandle handle = handles[(int)ShaderHandles.WorldViewProjection];
            if (handle != null)
                effect.SetValue(handle, item.Motion.WorldMatrix * Scene.CurrentInstance.Camera.Motion * Scene.CurrentInstance.Camera.Projection);

            handle = handles[(int)ShaderHandles.World];
            if (handle != null)
                effect.SetValue(handle, item.Motion.WorldMatrix);

            handle = handles[(int)ShaderHandles.View];
            if (handle != null)
                effect.SetValue(handle, Scene.CurrentInstance.Camera.Motion);

            handle = handles[(int)ShaderHandles.Projection];
            if (handle != null)
                effect.SetValue(handle, Scene.CurrentInstance.Camera.Projection);
            
            handle = handles[(int)ShaderHandles.ViewProjection];
            if (handle != null)
                effect.SetValue(handle, Scene.CurrentInstance.Camera.Motion * Scene.CurrentInstance.Camera.Projection);

            handle = handles[(int)ShaderHandles.ViewInverse];
            if (handle != null)
                effect.SetValue(handle, Matrix.Invert(Scene.CurrentInstance.Camera.Motion.WorldMatrix));

            handle = handles[(int)ShaderHandles.WorldInverseTranspose];
            if (handle != null)
                effect.SetValue(handle, Matrix.Transpose(Matrix.Invert(item.Motion.WorldMatrix)));

            handle = handles[(int)ShaderHandles.Diffuse];
            if (handle != null)
                effect.SetValue(handle, material.Diffuse);

            handle = handles[(int)ShaderHandles.Ambient];
            if (handle != null)
                effect.SetValue(handle, material.Ambient);

            handle = handles[(int)ShaderHandles.Specular];
            if (handle != null)
                effect.SetValue(handle, material.Specular);

            handle = handles[(int)ShaderHandles.Shininess];
            if (handle != null)
                effect.SetValue(handle, material.Shininess);

            for (int i = 0; i < Scene.CurrentInstance.DefaultLights.Count; i++)
            {
                handle = handles[(int)ShaderHandles.LightPosition0 + i];
                if (handle != null)
                    effect.SetValue(handle, new Vector4(Scene.CurrentInstance.DefaultLights[i].Motion.Translation, 1));

                handle = handles[(int)ShaderHandles.LightAmbient0 + i];
                if (handle != null)
                    effect.SetValue(handle, new Color4(Scene.CurrentInstance.DefaultLights[i].Ambient));
                handle = handles[(int)ShaderHandles.LightDiffuse0 + i];
                if (handle != null)
                    effect.SetValue(handle, new Color4(Scene.CurrentInstance.DefaultLights[i].Diffuse));
                handle = handles[(int)ShaderHandles.LightSpecular0 + i];
                if (handle != null)
                    effect.SetValue(handle, new Color4(Scene.CurrentInstance.DefaultLights[i].Specular));
                handle = handles[(int)ShaderHandles.LightAttenuation0 + i];
                if (handle != null)
                    effect.SetValue(handle, new Vector4(Scene.CurrentInstance.DefaultLights[i].Attenuation, 0));
            }



            handle = handles[(int)ShaderHandles.ElapsedTime];
            if (handle != null)
                effect.SetValue(handle, item.Timer.LastInterval);

            handle = handles[(int)ShaderHandles.Time];
            if (handle != null)
                effect.SetValue(handle, item.Timer.TotalTimeSeconds);


            for (int i = 0; i < material.Textures.Count; i++)
            {
                handle = handles[(int)ShaderHandles.Texture0 + i];
                if ((handle != null) && (material.Textures[i] != null))
                    effect.SetValue(handle, material.Textures[i]);
            }
        }

        /// <summary>
        /// \internal Returns the number of passes the shader has
        /// </summary>
        /// <param name="material">Material that will be used to render (specifies the technique that will be used)</param>
        /// <returns>Number of passes of the shader technique</returns>
        public override int PassNumber(Material material)
        {
            if (effect == null) return 0;
            EffectHandle tech=null;
            if (material.Technique != null)
                tech = effect.GetTechnique(material.Technique);
            if (tech==null)
                tech=effect.GetTechnique(0);

            return effect.GetTechniqueDescription(tech).Passes;
        }

        /// <summary>
        /// \internal Begins the render of the specified pass
        /// </summary>
        /// <param name="pass">Pass number</param>
        public override void BeginPass(int pass)
        {
            if (effect == null) return;
            effect.BeginPass(pass);
        }

        /// <summary>
        /// \internal Ends the render of the current pass
        /// </summary>
        public override void EndPass()
        {
            if (effect == null) return;
            effect.EndPass();
        }

        /// <summary>
        /// \internal Begins the render of the specified combination of item / material
        /// </summary>
        /// <param name="item">The rendered item</param>
        /// <param name="material">The material used in the render</param>
        public override void BeginRender(Item item, Material material)
        {
            if (effect == null) return;
            fillStandardVariables(item, material);
            fillCustomVariables(item, material);
            effect.Begin(FX.DoNotSaveState | FX.DoNotSaveShaderState | FX.DoNotSaveSamplerState);
        }

        /// <summary>
        /// \internal Ends the render of the item
        /// </summary>
        public override void EndRender()
        {
            if (effect == null) return;
            effect.End();            
        }


        /// <summary>
        /// \internal Clear resources
        /// </summary>
        public override void Dispose()
        {
            if (effect!=null)
                effect.Dispose();
            effect = null;
            foreach (EffectHandle eh in handles)
            {
                if (eh != null)
                    eh.Dispose();
            }
            handles = null;
            base.Dispose();
        }
    }
}
